import pygame
from pygame.locals import * 
from resource import * 
import traceback
import sys
import weakref
import inspect
DefaultContextId="default"

def IsPydevDebugMode():
    # Get the first file of the stack
    scriptpath=inspect.stack().pop()[1]    
    return os.path.basename(scriptpath)=="pydevd.py"

class ModelUpdateNotif:
    pass
   
def LogAbend(msg):
    print "An unexpected situation occured:"
    print msg
    traceback.print_stack()

class AgentType:
    resident=0
    exclusive=1

class AbstractAgent:    
    def __init__(self,type):
        self.type=type
        self.AC=None
        print self.__class__,"start"
    def __del__(self):
        print self.__class__,"end"
    def RequestDestruction(self):
        Appication().agent.Unregister(self)       
    def ProcesEvent(self,event, agentmanager):#to be overloaded    
        pass
                  
class AbstractObservable:
    def __init__(self):
        self.__observers_wr=[]
    def Register(self, observer):
        if not observer in [wr() for wr in self.__observers_wr]:
            self.__observers_wr.append(weakref.ref(observer,lambda wr:self.__observers_wr.remove(wr)))
    def Unregister(self, observer):
        observers_wr=[wr for wr in self.__observers_wr if wr() is not observer]        
    def Notify(self, observer):#to be overloaded
        pass
    def NotifyObservers(self):
        map(self.Notify, [wr() for wr in self.__observers_wr])
        
class TimerEventObservable(AbstractAgent, AbstractObservable):
    def __init__(self):
        AbstractAgent.__init__(self, AgentType.resident)
        AbstractObservable.__init__(self)        
    def InstallTimer(self):        
        self.eventid=pygame.USEREVENT+1
        pygame.time.set_timer(self.eventid, 200)
    def RemoveTimer(self):        
        pygame.time.set_timer(self.eventid, 0)
    def ProcesEvent(self,event, agentmanager):         
        if event.type!=self.eventid:  return 0 #signal not processed
        self.NotifyObservers()
        
        return 1 #signal processed
    def Notify(self, observer):
        observer.TimerUpdate()


class AbstractView:
    def __init__(self,model,contextId):
        self.contextId=contextId  
        self.model=model
        self.area=0 #drawing translation an area restruction
        self.pick=0 #Pick zone in local area's coordinates'
        self.childs=[]
        #register to model events
        model.RegisterView(self)
    def Build(self):
        pass
    def Draw(self, surface):
        if self.area:  surface=surface.subsurface(self.area)
        for c in self.childs:
            c.Draw(surface)  
    def QueryPickPath(self,pickpos):         
        if self.area:
            pickpos=(pickpos[0]-self.area.left, pickpos[1]-self.area.top)        
        paths=[]
        for c in self.childs:
            paths+=c.QueryPickPath(pickpos)         
        if self.pick and self.pick.collidepoint(*pickpos):            
            paths.append(self)        
        return paths
    def ProcessModelNotif(self, notif):
        if notif.__class__ is ModelUpdateNotif:
            self.Build()
    def ActivateModel(self):
        try:
            fn=self.model.Activate
        except AttributeError:
            print "Model %s doesn't implement Activate protocol"%self.model.__class__
            return 0
        return fn()
    def ScrollModel(self,p):
        try:
            fn=self.model.Scroll
        except AttributeError:
            print "Model %s doesn't implement Scrolling protocol"%self.model.__class__
            return 0
        return fn(p)
    
class AbstractSpriteView(AbstractView):
    def __init__(self,model,contextId):        
        AbstractView.__init__(self, model,contextId)        
    def GetPosition(self):
        return (0,0)
    def GetSurface(self):
        return None
    def GetAngle(self):
        return 45
    def Draw(self, surface):
        if self.area:  surface=surface.subsurface(self.area)
        rotatedsurf=pygame.transform.rotate(self.GetSurface(), self.GetAngle())
        pos=self.GetPosition()
        size=rotatedsurf.get_size()
        pygame.Surface.blit(surface,rotatedsurf,(pos[0]-.5*size[0],pos[1]-.5*size[1]))
        for c in self.childs:
            c.Draw(surface)        
    
class AbstractCompiledView(AbstractView):
    def __init__(self,model,contextId):        
        AbstractView.__init__(self, model,contextId)
        self.primitives=list()
    def DrawPrimitive(self,  fn, arg):
        self.primitives.append((fn, arg))
    def DrawText(self,pos,text,font,color=(255,255,255)):
        x=pos[0]
        y=pos[1]        
        for line in text.split('\n'):
            renderedtext=font.render(line,0,color)            
            self.DrawPrimitive(pygame.Surface.blit, (renderedtext, (x, y)))
            y+=renderedtext.get_height()
        return y
    def ClearPrimitives(self):
        self.primitives=list()
    def Draw(self, surface):        
        if self.area: surface=surface.subsurface(self.area)
        for fn, arg in self.primitives:
            fn(surface,*arg)        
        for c in self.childs:
            c.Draw(surface)            
    
class AbstractMultiframeView(AbstractCompiledView):
    def __init__(self, model,contextId):
        AbstractCompiledView.__init__(self, model,contextId)
        self.frames=list()
    def ClearFrames(self):
        self.frames=list()
        self.primitives=list()               
    def GotoFrame(self, framenumber):
        self.pick, self.primitives=self.frames[framenumber]        
    def PushFrame(self):
        self.frames.append((self.pick, self.primitives))
        AbstractCompiledView.ClearPrimitives(self) 
    def GetFrame(self):
        return 0
    def Draw(self, surface):
        frame=self.GetFrame()
        if frame<0: return
        if frame>=len(self.frames):
            pass
        self.pick, self.primitives=self.frames[frame]
        
        AbstractCompiledView.Draw(self, surface) 
    def DrawPrimitiveOnAllFrames(self,  fn, arg):
        if len(self.frames):
            for frame in self.frames:
                frame[1].append((fn, arg))            
        else:
            self.primitives.append((fn, arg))            
    
class AbstractAnimatedView(AbstractMultiframeView):
    def __init__(self,model,contextId):
        AbstractMultiframeView.__init__(self,model,contextId)
        self.currentindex=0        
    def GotoFrame(self, framenumber):
        self.currentindex=framenumber%len(self.frames)
        AbstractMultiframeView.GotoFrame(self, framenumber)
    def GetFrame(self):   
        return self.currentindex
    def Play(self):
        GetApplication().timerdipatcher.Register(self)   
    def Stop(self):                
        GetApplication().timerdipatcher.Unregister(self)
    def TimerUpdate(self):
        if len(self.frames):
            self.currentindex=(self.currentindex+1)%len(self.frames)            
    def ClearFrames(self):
        AbstractMultiframeView.ClearFrames(self)
        self.Stop()
    def PushFrame(self):
        AbstractMultiframeView.PushFrame(self)
        if(len(self.frames)==2):
            self.Play()
    def Build(self):
        self.currentindex=0
        AbstractMultiframeView.Build(self)
        
        
            
class AbstractModel:        
    def __init__(self,controler=0):
        self.controler=controler
        self.__views_wr=[]
    def RegisterView(self,v):
        self.__views_wr.append(weakref.ref(v,lambda v_wr:self.__views_wr.remove(v_wr)))
    def _GetViewClass(self,ContextId):
        pass
    def BuildView(self,contextId):
        v=self._GetViewClass(contextId)(self,contextId)
        v.Build()
        return v
    def NotifyViews(self,notif):
        for v_wr in self.__views_wr:
            v=v_wr()
            if v: 
                v.ProcessModelNotif(notif)

class AbstractControler(object):
    __slots__=["__modelweakref"]
    def __init__(self,model):
        self.__modelweakref=weakref.ref(model)
    @property
    def model(self):
        """provide the current model"""
        return self.__modelweakref()
        
class AbstractDynamicControler(AbstractControler):
    def Update(self):
        pass
    
class Application:
    def __init__(self):
        print "===== Start application  ====="
        #create the ressource manager
        self.resourcemanager=ResourceManager() 
        #Build sub elements
        self.game=None
        self.agent=AgentManager(self)
        #ground animation event handler
        self.timerdipatcher=TimerEventObservable()
        self.timerdipatcher.InstallTimer()
        self.agent.Register(self.timerdipatcher)
        
        pygame.init()
        pygame.font.init()
        try:
            pygame.mixer.init()            
        except:
            print >> sys.stderr, "An error occured while initialisating the sound system."
            print >> sys.stderr, "Sound is disabled."            
            pygame.error()
        
    def QueryPickPath(self, pickpos):
        return self.view.QueryPickPath(pickpos)
    
    def PlayMusic(self,filepath):
        if pygame.mixer.get_init(): #check if sound is enable
            pygame.mixer.music.load(filepath)
            pygame.mixer.music.play()
    
    def PlaySound(self,soundobject):
        if pygame.mixer.get_init(): #check if sound is enable
            soundobject.play()
            
    def MusicFadeOut(self,millisec):
        if pygame.mixer.get_init(): #check if sound is enable
            pygame.mixer.fadeout(millisec)
        
            
    def Run(self,game):
        self.game=game
        fnt=self.resourcemanager.Load("freesansbold.ttf:10")
        
        window = pygame.display.set_mode((1200,900))
        screen = pygame.display.get_surface()      
        print "Video driver:",pygame.display.get_driver()        
        print pygame.display.Info()        
        self.view=self.game.BuildView(DefaultContextId)
        clock=pygame.time.Clock()
        
        if "profiling" in os.environ:
            countdown=int(os.environ["profiling"])
            self.game.controler.SetGameState(1)
        else:
            countdown=0
    
        while True:
            #process user events
            for event in pygame.event.get():
                #print event
                self.agent.ProcesEvent(event, self.agent)
            #redraw all the content
            screen.fill((0,0,0))            
            
            self.game.controler.Update()            
            self.view.Draw(screen)     
                   
            #draw the status bar  
            renderedtext=fnt.render("FPS: %d - Gamestate: %d"%(clock.get_fps(),self.game.state),0,(255,255,255))            
            statusbar=screen.subsurface(0,screen.get_height()-renderedtext.get_height(),screen.get_width(),renderedtext.get_height())
            screen.blit(renderedtext, (0,screen.get_height()-renderedtext.get_height()))            
            clock.tick()        
            #clip de surface    
            pygame.display.flip()
            
            #profiling extention
            if countdown:
                countdown-=1
                if not countdown:
                    break
        
        
    
def PostNotification( object, fn, args ):    
    object.__dict__[fn](*args)    
    for o in object:
        o.__dict__[fn](*args)
        
class ActivateAgent(AbstractAgent):
    def __init__(self):
        AbstractAgent.__init__(self,AgentType.resident)
    def ProcesEvent(self,event, agentmanager):        
        if event.type==MOUSEBUTTONDOWN:
            if event.mouse.button==1:
                for e in event.pick:
                    agent = e.ActivateModel()
                    if agent:  
                        agentmanager.Register(agent)
                        break
            elif event.mouse.button==4 or event.mouse.button==5:
                p=(event.mouse.button-4)*2-1
                for e in event.pick:
                    if e.ScrollModel(p):
                        break
                    
            

class MouseEvent:
    def __init__(self, pyevent, pick):
        self.pick=pick
        self.type=pyevent.type        
        self.mouse=pyevent

           
    
class AgentManager(AbstractAgent):
    def __init__(self,application):        
        AbstractAgent.__init__(self,AgentType.resident)       
        self.application=application       
        self.after=[ActivateAgent()]
        self.current=[]
        self.resident=[]
        
    def Unregister(self,agent):
        self.after.remove(agent)
        self.current.remove(agent)
        self.before.remove(agent)
        
    def Register(self, agent):
        if agent.type==AgentType.exclusive:
            self.current=[agent]
        elif agent.type==AgentType.resident:
            self.resident.append(agent)
        else:
            LogAbend("Unknown agent type")
               
    def ProcesEvent(self,event, agentmanager):        
        if event.type == QUIT: 
            sys.exit(0)
        elif event.type == MOUSEBUTTONDOWN or event.type == MOUSEBUTTONUP or event.type == MOUSEMOTION:            
            event=MouseEvent(event, self.application.QueryPickPath(event.pos))
        for a in self.resident+self.current+self.after:#optim
            if a.ProcesEvent(event, agentmanager): break

"""Application singleton"""
applicationinstance = None

def GetApplication():
    """return the application singleton"""
    global applicationinstance
    if applicationinstance is None:
       applicationinstance = Application()
    return applicationinstance

